#pragma once
#include "BVHHandle.h"
#include "DL.Lx.h"

#include <cliext\vector>
using namespace cliext;
namespace DLLx {
	namespace Bvh {

		ref class  Vec3 {
		public:
			float x, y, z;

			// Vec3 Public Methods
			Vec3(float _x , float _y , float _z )
				: x(_x), y(_y), z(_z) {
			}

			Vec3(float a)
				:x(a),y(a),z(a)
			{

			}

			Vec3(Vec3^ v) :
				x(v->x), y(v->y),z(v->z)

			{	
			}

			Vec3(Vec3^% v) :
				x(v->x), y(v->y),z(v->z)

			{	
			}

			//			explicit Vec3(const Pt ^p);
			Vec3^ operator+(const Vec3^ v)  {
				return gcnew Vec3(x + v->x, y + v->y, z + v->z);
			}

			Vec3^ operator+=(const Vec3^ v) {
				x += v->x; y += v->y; z += v->z;
				return this;
			}
			Vec3^ operator-(const Vec3^ v)  {
				return gcnew Vec3(x - v->x, y - v->y, z - v->z);
			}

			Vec3^ operator-=(const Vec3 ^v) {
				x -= v->x; y -= v->y; z -= v->z;
				return this;
			}
			bool operator==(const Vec3 ^v)  {
				return x == v->x && y == v->y && z == v->z;
			}
			static Vec3^ operator*(Vec3^ v,float f)  {
				return gcnew Vec3(f*v->x, f*v->y, f*v->z);
			}

			Vec3^ operator*(float f)  {
				return gcnew Vec3(f*x, f*y, f*z);
			}
			static inline Vec3^ operator*(float f, const Vec3^ v) {
				return gcnew Vec3(f*v->x, f*v->y, f*v->z);
			}

			Vec3^ operator*=(float f) {
				x *= f; y *= f; z *= f;
				return this;
			}
			Vec3^ operator/(float f)  {
				float inv = 1.f / f;
				return gcnew Vec3(x * inv, y * inv, z * inv);
			}
			static Vec3^ operator/(const Vec3^ v, float f)  {
				float inv = 1.f / f;
				return gcnew Vec3(v->x * inv, v->y * inv, v->z * inv);
			}

			Vec3^ operator/=(float f) {
				float inv = 1.f / f;
				x *= inv; y *= inv; z *= inv;
				return this;
			}
			Vec3^ operator-()  {
				return gcnew Vec3(-x, -y, -z);
			}

			inline float operator[](int i)  {
				if (i == 0 )
					return x;
				if (i == 1)
					return y;
				if (i==2)
					return z;
				return x;
			}


			float LengthSquared()  { return x*x + y*y + z*z; }
			float Length()  { return sqrtf(LengthSquared()); }
			//explicit Vec3(const Normal^n);
			inline float Dot(const Vec3 ^v1, const Vec3 ^v2) {
				return v1->x * v2->x + v1->y * v2->y + v1->z * v2->z;
			}

			inline float AbsDot(const Vec3 ^v1, const Vec3 ^v2) {
				return fabsf(Dot(v1, v2));
			}

			inline Vec3^ Cross(const Vec3 ^v1, const Vec3 ^v2) {
				return gcnew Vec3((v1->y * v2->z) - (v1->z * v2->y),
					(v1->z * v2->x) - (v1->x * v2->z),
					(v1->x * v2->y) - (v1->y * v2->x));
			}

			inline Vec3^ Normalize(Vec3 ^v) {
				float l = v->Length();
				return v / l;
			}

			// Vec3 Public Data
		};

		ref class Pt
		{
		public:
			float x,y,z;
			Pt(float _x , float _y , float _z )
				: x(_x), y(_y), z(_z) {
			}

			Pt(float v[3]) : x(v[0]), y(v[1]), z(v[2]) {
			}

			Pt^ operator+( Vec3 ^v)  {
				return gcnew Pt(x + v->x, y + v->y, z + v->z);
			}

			Pt ^ operator+=(const Vec3 ^v) {
				x += v->x;
				y += v->y;
				z += v->z;
				return this;
			}

			Vec3^ operator-(const Pt ^p)  {
				return gcnew Vec3(x - p->x, y - p->y, z - p->z);
			}
			inline static Vec3^ operator-(const Pt^ p1,const Pt ^p)  {
				return gcnew Vec3( p1->x - p->x, p1->y - p->y, p1->z - p->z);
			}
			Pt^ operator-(const Vec3 ^v)  {
				return gcnew Pt(x - v->x, y - v->y, z - v->z);
			}

			Pt ^ operator-=(const Vec3 ^v) {
				x -= v->x;
				y -= v->y;
				z -= v->z;
				return this;
			}
			static inline Pt^ operator*(float f, const Pt ^v) {
				return gcnew Pt(f*v->x, f*v->y, f*v->z);
			}
			Pt ^ operator+=(const Pt ^p) {
				x += p->x;
				y += p->y;
				z += p->z;
				return this;
			}

			Pt ^ operator-=(const Pt ^p) {
				x -= p->x;
				y -= p->y;
				z -= p->z;
				return this;
			}

			Pt^ operator+(const Pt ^p) {
				return gcnew Pt(x + p->x, y + p->y, z + p->z);
			}

			Pt^ operator*(float f)  {
				return gcnew Pt(f*x, f*y, f * z);
			}

			Pt ^ operator*=(float f) {
				x *= f;
				y *= f;
				z *= f;
				return this;
			}

			Pt^ operator/(float f)  {
				float inv = 1.f / f;
				return gcnew Pt(inv*x, inv*y, inv * z);
			}

			Pt ^ operator/=(float f) {
				float inv = 1.f / f;
				x *= inv;
				y *= inv;
				z *= inv;
				return this;
			}
			static inline float Distance(const Pt ^p1, const Pt ^p2) {
				return (p1 - p2)->Length();
			}

			static inline float DistanceSquared(const Pt ^p1, const Pt ^p2) {
				return (p1 - p2)->LengthSquared();
			}
			float operator[](int i)  {
				if (i == 0 )
					return x;
				if (i == 1)
					return y;
				if (i==2)
					return z;
				return x;
			}
		};

		ref class  BBox {
		public:
			Pt^ pMin;
			Pt^ pMax;

			// BBox Public Methods
			BBox() {
				pMin = gcnew Pt( INFINITY,  INFINITY,  INFINITY);
				pMax = gcnew Pt(-INFINITY, -INFINITY, -INFINITY);
			}
			BBox( Pt ^p) : pMin(p), pMax(p) { }
			BBox( Pt ^p1, Pt ^p2) {
				pMin = gcnew Pt(min(p1->x, p2->x),
					min(p1->y, p2->y),
					min(p1->z, p2->z));
				pMax = gcnew Pt(max(p1->x, p2->x),
					max(p1->y, p2->y),
					max(p1->z, p2->z));
			}
			BBox(const Pt ^p1, const Pt ^p2) {
				pMin = gcnew Pt(min(p1->x, p2->x),
					min(p1->y, p2->y),
					min(p1->z, p2->z));
				pMax = gcnew Pt(max(p1->x, p2->x),
					max(p1->y, p2->y),
					max(p1->z, p2->z));
			}

			static BBox^ Union(const BBox ^b, const Pt ^p)
			{
				BBox^ ret = gcnew BBox(b->pMin, b->pMax);
				ret->pMin->x = min(b->pMin->x, p->x);
				ret->pMin->y = min(b->pMin->y, p->y);
				ret->pMin->z = min(b->pMin->z, p->z);
				ret->pMax->x = max(b->pMax->x, p->x);
				ret->pMax->y = max(b->pMax->y, p->y);
				ret->pMax->z = max(b->pMax->z, p->z);
				return ret;
			}

			static BBox^ Union(const BBox ^b, const BBox ^b2)
			{
				BBox^ ret = gcnew BBox(b->pMin, b->pMax);
				ret->pMin->x = min(b->pMin->x, b2->pMin->x);
				ret->pMin->y = min(b->pMin->y, b2->pMin->y);
				ret->pMin->z = min(b->pMin->z, b2->pMin->z);
				ret->pMax->x = max(b->pMax->x, b2->pMax->x);
				ret->pMax->y = max(b->pMax->y, b2->pMax->y);
				ret->pMax->z = max(b->pMax->z, b2->pMax->z);
				return ret;
			}

			bool Overlaps(const BBox ^b)  {
				bool x = (pMax->x >= b->pMin->x) && (pMin->x <= b->pMax->x);
				bool y = (pMax->y >= b->pMin->y) && (pMin->y <= b->pMax->y);
				bool z = (pMax->z >= b->pMin->z) && (pMin->z <= b->pMax->z);
				return (x && y && z);
			}
			bool Inside(const Pt ^pt)  {
				return (pt->x >= pMin->x && pt->x <= pMax->x &&
					pt->y >= pMin->y && pt->y <= pMax->y &&
					pt->z >= pMin->z && pt->z <= pMax->z);
			}
			void Expand(float delta) {
				pMin -=gcnew  Vec3(delta, delta, delta);
				pMax +=gcnew Vec3(delta, delta, delta);
			}
			float Volume()  {
				Vec3^ d = pMax - pMin;
				return d->x * d->y * d->z;
			}
			float SurfaceArea()  {
				Vec3^ d = pMax - pMin;
				return 2.f * (d->x * d->y + d->y * d->z + d->z * d->x);
			}
			int MaximumExtent() {
				Vec3^ diag = pMax - pMin;
				if (diag->x > diag->y && diag->x > diag->z)
					return 0;
				else if (diag->y > diag->z)
					return 1;
				else
					return 2;
			}
			/*	void BoundingSphere([Out]Pt%^ *c, float *rad)
			{

			}*/
			/*bool IntersectP(const Ray ^ray,
			float *hitt0 = NULL,
			float *hitt1 = NULL) ;*/
			// BBox Public Data

		};

		ref struct BVHAccelTreeNode {
			BBox^ bbox;
			unsigned int primitive;
			BVHAccelTreeNode ^leftChild;
			BVHAccelTreeNode ^rightSibling;
		};

		ref struct BVHAccelArrayNode {
			BBox bbox;
			unsigned int primitive;
			unsigned int skipIndex;
		}; 

		public ref class BvhBuilder
		{
		public:

			unsigned int treeType;
			int costSamples, isectCost, traversalCost;
			float emptyBonus;
			unsigned int nPrims;
			unsigned int nNodes;
			array<Pt^>^ points;
			array<TriInfo^>^ triangles;
			array<BVHAccelArrayNode^>^ bvhTree;
			BVHAccelTreeNode ^debugTree;
			BvhBuilder(const unsigned int vCount, const unsigned int triangleCount,  array<TriInfo^>^ p,  array<Pt^>^ v,
				const unsigned int treetype, const int csamples, const int icost,
				const int tcost, const float ebonus);

			BVHAccelTreeNode^ BuildHierarchy(cliext::vector<BVHAccelTreeNode^>^list, unsigned int begin, unsigned int end, unsigned int axis);
			void FindBestSplit(cliext::vector<BVHAccelTreeNode ^>^list, unsigned int begin, unsigned int end, float^% splitValue, unsigned int^% bestAxis);
			unsigned int BuildArray(BVHAccelTreeNode ^node, unsigned int offset);
		};
	}
}